#include <cassert>

#include "CSpriteManager.h"

#include "CSprite.h"
#include "CBlockView.h"


#include <set>

using namespace std;

CSpriteManager::CSpriteManager(const string& content_path, const string& extension)
: ContentPath(content_path)
, Extension(extension)
{
	Sprites.reserve(SIZE_SPRITE_TYPE);
}


void DrawPixel(SDL_Surface *Surface, int x, int y,
			   Uint8 R, Uint8 G, Uint8 B, Uint8 A)
{
	Uint32 color = SDL_MapRGBA(Surface->format, R, G, B, A);
	Uint8 *p= (Uint8 *)Surface->pixels + y*Surface->pitch + x*Surface->format->BytesPerPixel;
    assert(Surface->format->BytesPerPixel == 4);
	*(Uint32 *)p = color;
    return;
}
void GetAlpha(SDL_Surface *Surface, int x, int y,
			   Uint8& A)
{
	// Uint32 color = SDL_MapRGBA(Surface->format, R, G, B, A);
	Uint8 *p= (Uint8 *)Surface->pixels + y*Surface->pitch + x*Surface->format->BytesPerPixel;
    assert(Surface->format->BytesPerPixel == 4);
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
	A = *p;
#else
	A = *(p + 3);
#endif
    return;
}

void SetAlpha(SDL_Surface *Surface, int x, int y, Uint8 A)
{
	Uint8 *p= (Uint8 *)Surface->pixels + y*Surface->pitch + x*Surface->format->BytesPerPixel;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
	*p = A;
#else
	*(p + 3) = A;
#endif
}

void print_surface_info(SDL_Surface *surf, const char* surf_name)
{
	printf("%s.flags = %s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", surf_name,
		surf->flags & SDL_ANYFORMAT ? "SDL_ANYFORMAT " : "",
		surf->flags & SDL_ASYNCBLIT ? "SDL_ASYNCBLIT " : "",
		surf->flags & SDL_DOUBLEBUF ? "SDL_DOUBLEBUF " : "",
		surf->flags & SDL_HWACCEL ? "SDL_HWACCEL " : "",
		surf->flags & SDL_HWPALETTE ? "SDL_HWPALETTE " : "",
		surf->flags & SDL_HWSURFACE ? "SDL_HWSURFACE " : "",
		surf->flags & SDL_FULLSCREEN ? "SDL_FULLSCREEN " : "",
		surf->flags & SDL_OPENGL ? "SDL_OPENGL " : "",
		surf->flags & SDL_OPENGLBLIT ? "SDL_OPENGLBLIT " : "",
		surf->flags & SDL_RESIZABLE ? "SDL_RESIZABLE " : "",
		surf->flags & SDL_RLEACCEL ? "SDL_RLEACCEL " : "",
		surf->flags & SDL_SRCALPHA ? "SDL_SRCALPHA " : "",
		surf->flags & SDL_SRCCOLORKEY ? "SDL_SRCCOLORKEY " : "",
		surf->flags & SDL_SWSURFACE ? "SDL_SWSURFACE " : "",
		surf->flags & SDL_PREALLOC ? "SDL_PREALLOC " : ""
		);
	printf("%s.pixelformat: \n", surf_name);
	printf("\tBitsPerPixel = %d\n", surf->format->BitsPerPixel);
	printf("\tBytesPerPixel = %d\n", surf->format->BytesPerPixel);
	printf("\talpha = %d\n", surf->format->alpha);
	printf("\tRshift = %d\n", surf->format->Rshift);
	printf("\tGshift = %d\n", surf->format->Gshift);
	printf("\tBshift = %d\n", surf->format->Bshift);
	printf("\tAshift = %d\n", surf->format->Ashift);

}


SDL_Rect IntersectRects(const SDL_Rect& a, const SDL_Rect& b)
{
	int l = std::max(a.x, b.x);
	int t = std::max(a.y, b.y);
	int r = std::min(a.x + a.w, b.x + b.w);
	int bt = std::min(a.y + a.h, b.y + b.h);

	SDL_Rect res;
	res.x = l;
	res.y = t;
	res.w = r - l;
	res.h = bt - t;

	return res;
}

SDL_Surface* CSpriteManager::CreateNewSurface()
{
	/* SDL interprets each pixel as a 32-bit number, so our masks must depend
       on the endianness (byte order) of the machine */
	Uint32 rmask, gmask, bmask, amask;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
    rmask = 0xff000000;
    gmask = 0x00ff0000;
    bmask = 0x0000ff00;
    amask = 0x000000ff;
#else
    rmask = 0x000000ff;
    gmask = 0x0000ff00;
    bmask = 0x00ff0000;
    amask = 0xff000000;
#endif

	surf_w = shadows_left_size*2*50, surf_h = 800, surf_x = 0, surf_y = 0;

	SDL_Surface *surf = SDL_CreateRGBSurface(
		SDL_SWSURFACE,
		surf_w, surf_h,
		32, rmask, gmask, bmask, amask);

	assert(surf);

	SDL_LockSurface(surf);
	for (int i = 0; i < surf_w; ++i)
		for (int j = 0; j < surf_h; ++j)
			DrawPixel(surf,i,j, 128,128,128,255);
	SDL_UnlockSurface(surf);

    print_surface_info(surf, "Block surface");

	return surf;
}

void CopyAphaBits(SDL_Surface *surf, SDL_Surface *block,const SDL_Rect& block_rect,const SDL_Rect& dest_rect)
{
	SDL_LockSurface(surf);
	SDL_LockSurface(block);

	for (int i = 0; i < block_rect.w; ++i)
		for (int j = 0; j < block_rect.h; ++j)
		{
			Uint8 a;
			GetAlpha(block, block_rect.x + i, block_rect.y + j, a);
			SetAlpha(surf, dest_rect.x + i, dest_rect.y + j, a);
		}

	SDL_UnlockSurface(block);
	SDL_UnlockSurface(surf);
}


#define SIZEOF(val)	sizeof(val)/sizeof(val[0])

void CSpriteManager::BuildBlocksSprites()
{
	assert (SIZEOF(shadows_left) == shadows_left_size);
	assert (SIZEOF(shadows_right) == shadows_left_size);
	assert (SIZEOF(shadows_side) == shadows_side_size);


	SDL_Surface *surf = CreateNewSurface();
	SurfaceBlocks = surf;

	int max_block_h = 0;
	int rect_num = 0;
	for (int i = 0; i < B_SIZE_BLOCK; ++i)
	{
		
		SDL_Rect dest_rect;
		std::set<SpriteType> shadows;
		const uint16_t *shadows_variants = 0;

		// build top shadows on blocks...
		for (int lr = 0; lr < 2; ++lr)
		{
			SDL_Surface *block_surf = Get(BlockSprites[i])->GetSurface();
			SDL_Rect block_rect = Get(BlockSprites[i])->GetRect();
			block_rect.w = block_rect.w / 2;
			block_rect.h = block_rect.h - 40;
			if (!lr)
				shadows_variants = shadows_left;
			else {
				block_rect.x = block_rect.w;
				shadows_variants = shadows_right;
			}
			for (int j = 0; j < shadows_left_size; ++j)
			{
				dest_rect.x = surf_x;
				dest_rect.y = surf_y;
				SDL_BlitSurface(block_surf, &block_rect, surf, &dest_rect);

				if (*(shadows_variants + j) & S_SHADOW_WEST)			shadows.insert(SHADOW_WEST);
				if (*(shadows_variants + j) & S_SHADOW_SOUTH_WEST)		shadows.insert(SHADOW_SOUTH_WEST);
				if (*(shadows_variants + j) & S_SHADOW_SOUTH_EAST)		shadows.insert(SHADOW_SOUTH_EAST);
				if (*(shadows_variants + j) & S_SHADOW_SOUTH)			shadows.insert(SHADOW_SOUTH);
				if (*(shadows_variants + j) & S_SHADOW_NORTH_WEST)		shadows.insert(SHADOW_NORTH_WEST);
				if (*(shadows_variants + j) & S_SHADOW_NORTH_EAST)		shadows.insert(SHADOW_NORTH_EAST);
				if (*(shadows_variants + j) & S_SHADOW_NORTH)			shadows.insert(SHADOW_NORTH);
				if (*(shadows_variants + j) & S_SHADOW_EAST)			shadows.insert(SHADOW_EAST);
				if (*(shadows_variants + j) & S_SHADOW_SIDE_WEST)		shadows.insert(SHADOW_SIDE_WEST);

				

				for (std::set<SpriteType>::const_iterator k = shadows.begin(); 
					k != shadows.end(); ++k)
				{
					SDL_Surface* shadow_surf = Get(*k)->GetSurface();
					SDL_Rect r = Get(*k)->GetRect();
					SDL_Rect shadow_rect = IntersectRects(r, block_rect);

					SDL_Rect shadow_dst_rect;
					shadow_dst_rect.x = surf_x - (block_rect.x - shadow_rect.x);
					shadow_dst_rect.y = surf_y - (block_rect.y - shadow_rect.y);

					int ret = SDL_BlitSurface(shadow_surf, &shadow_rect, surf, &shadow_dst_rect);
                    if (ret)
                        fprintf(stderr, "blitsurface false: ret code = %d\n", ret);
                    /*
					if ((*k != S_SHADOW_SOUTH_EAST)|(*k != S_SHADOW_SOUTH_WEST)|(*k != S_SHADOW_SOUTH)|(*k != S_SHADOW_NORTH) 
						&& shadow_rect.y > block_rect.y) {
						printf("new shadow\n");
					}
                    */

				}
				shadows.clear();
				// clear alfa bit like block_rect;
				CopyAphaBits(surf, block_surf, block_rect, dest_rect);
				SDL_Rect t = {dest_rect.x, dest_rect.y, block_rect.w, block_rect.h}; // x, y, w, h
				BlockSprite r = { block_rect.x, block_rect.y, t};
				block_rects[rect_num++] = r;

				max_block_h = std::max(max_block_h, (int)block_rect.h);
				surf_x += block_rect.w;
				if (surf_x + block_rect.w > surf_w)
				{
					surf_x = 0;
					
					if (surf_y + max_block_h > surf_h)
						break;
					surf_y += max_block_h;
					max_block_h = 0;
				}
			}
		}
	}
	
	for (int i = 0; i < B_SIZE_BLOCK; ++i)
	{
		SDL_Rect dest_rect;
		std::set<SpriteType> shadows;
		// build side shadows on block
		for (int j = 0; j < shadows_side_size; ++j)
		{
			SDL_Surface *block_surf = Get(BlockSprites[i])->GetSurface();
			SDL_Rect block_rect = Get(BlockSprites[i])->GetRect();
			block_rect.y = block_rect.y + block_rect.h - 40;
			block_rect.h = 40;
			dest_rect.x = surf_x;
			dest_rect.y = surf_y;

			SDL_BlitSurface(block_surf, &block_rect, surf, &dest_rect);

			if (shadows_side[j] & S_SHADOW_SIDE_NORTH)	shadows.insert(SHADOW_NORTH);
			if (shadows_side[j] & S_SHADOW_SIDE_WEST)	shadows.insert(SHADOW_SIDE_WEST);

			for (std::set<SpriteType>::const_iterator k = shadows.begin(); 
				k != shadows.end(); ++k)
			{
				SDL_Surface* shadow_surf = Get(*k)->GetSurface();
				SDL_Rect shadow_rect = Get(*k)->GetRect();
				if (*k == SHADOW_NORTH)
					shadow_rect.h = 41;

				SDL_Rect shadow_dst_rect;
				shadow_dst_rect.x = surf_x - (block_rect.x - shadow_rect.x);
				shadow_dst_rect.y = surf_y - (block_rect.y - shadow_rect.y);
				if (*k == SHADOW_NORTH)
					shadow_dst_rect.y += 120;
				

				SDL_BlitSurface(shadow_surf, &shadow_rect, surf, &shadow_dst_rect);
			}
			shadows.clear();
			// clear alfa bit like block_rect;
			CopyAphaBits(surf, block_surf, block_rect, dest_rect);
			SDL_Rect t = {dest_rect.x, dest_rect.y, block_rect.w, block_rect.h}; // x, y, w, h
			BlockSprite r = { block_rect.x, block_rect.y, t};
			block_rects[rect_num++] = r;
			
			max_block_h = std::max(max_block_h, (int)block_rect.h);
			surf_x += block_rect.w;
			if (surf_x + block_rect.w > surf_w)
			{
				surf_x = 0;
				
				if (surf_y + max_block_h > surf_h)
					break;
				surf_y += max_block_h;
				max_block_h = 0;
			}
		}
	}

	// printf("rect_num = %d, %d\n", rect_num,sizeof(ShadowsRects)/sizeof(ShadowsRects[0]));
	assert(SIZEOF(block_rects) == rect_num);

	SDL_SaveBMP(surf, "surf.bmp");

	return;
}

void CSpriteManager::LoadSprites()
{
	const char* fnames[SIZE_SPRITE_TYPE] = 
	{
		"Brown Block",
		"Character Boy",
		"Character Cat Girl",
		"Character Horn Girl",
		"Character Pink Girl",
		"Character Princess Girl",
		"Chest Closed",
		"Chest Lid",
		"Chest Open",
		"Dirt Block",
		"Door Tall Closed",
		"Door Tall Open",
		"Enemy Bug",
		"Gem Blue",
		"Gem Green",
		"Gem Orange",
		"Grass Block",
		"Heart",
		"Key",
		"Plain Block",
		"Ramp East",
		"Ramp North",
		"Ramp South",
		"Ramp West",
		"Rock",
		"Roof East",
		"Roof North",
		"Roof North East",
		"Roof North West",
		"Roof South",
		"Roof South East",
		"Roof South West",
		"Roof West",
		"Selector",
		"Shadow East",
		"Shadow North",
		"Shadow North East",
		"Shadow North West",
		"Shadow Side West",
		"Shadow South",
		"Shadow South East",
		"Shadow South West",
		"Shadow West",
		"SpeechBubble",
		"Star",
		"Stone Block",
		"Stone Block Tall",
		"Tree Short",
		"Tree Tall",
		"Tree Ugly",
		"Wall Block",
		"Wall Block Tall",
		"Water Block",
		"Window Tall",
		"Wood Block"
	};

	assert( SIZE_SPRITE_TYPE == sizeof(fnames)/sizeof(*fnames));

	for (size_t i = 0; i < SIZE_SPRITE_TYPE; ++i) {
		string fname = ContentPath + '/' + fnames[i] + '.' + Extension;
		Sprites.push_back(new CSprite(fname));
	}

	BuildBlocksSprites();
}

const CSprite* CSpriteManager::Get(SpriteType t) const
{
	if (t < SIZE_SPRITE_TYPE)
		return Sprites[t];
	else
		return 0;
}



CSpriteManager::~CSpriteManager()
{
	for (scontainer::const_iterator i = Sprites.begin(); i != Sprites.end(); ++i)
		delete (*i);

	if (SurfaceBlocks)
		SDL_FreeSurface(SurfaceBlocks);
}



void CSpriteManager::DrawBlock(const CBlockView &b, SDL_Surface *surf, int x, int y)
{
	BlockSprite left  = block_rects[b.LeftPart()];
	BlockSprite right = block_rects[b.RightPart()];
	BlockSprite side  = block_rects[b.SidePart()];

	SDL_Rect dst = {x+left.x,y+left.y,0,0};
	SDL_BlitSurface(SurfaceBlocks, &left.r, surf, &dst);
	dst.x = x + right.x;
	dst.y = y + right.y;
	SDL_BlitSurface(SurfaceBlocks, &right.r, surf, &dst);
	dst.x = x + side.x;
	dst.y = y + side.y;
	SDL_BlitSurface(SurfaceBlocks, &side.r, surf, &dst);
}